今天要繼續讓控制面板真正影響時間
也就是加入:
▶ Play
⏸ Pause
⏩ Speed
讓使用者可以暫停整個太陽系,也可以自由調整行星運行速度。
由於目前專案程式碼已逐漸增加,從 Day 23 開始文章只列出當日新增或修改的程式碼。
完整整合版將於 Day 30 最終完成時提供。
今天會完成:
今天完成後控制面板會變成:
Solar System Controls
☑ 顯示軌道
☑ 金星大氣層
☑ 星空背景────────────────
⏸ Pause
Animation Speed
──────●──────
1.0x
| 項目 | 修改內容 |
|---|---|
| HTML | 新增 Pause 按鈕 |
| HTML | 新增 Speed Slider |
| CSS | 新增按鈕與 Slider 樣式 |
| JavaScript | 新增 isPlaying |
| JavaScript | 新增 timeScale |
| Animation | 使用速度倍率控制 delta |
首先在 Day 24 的控制面板下面加入:
<hr>
<button id="playPauseButton">
⏸ Pause
</button>
一開始動畫本來就在運行。
所以按鈕初始顯示:⏸ Pause
代表目前正在播放,如果按下去可以暫停
JavaScript 新增:
let isPlaying =
true;
這個變數用來記錄:
true → 動畫運行
false → 動畫暫停
接著取得按鈕:
const playPauseButton =
document.getElementById(
"playPauseButton"
);
加入:
playPauseButton.addEventListener(
"click",
() => {
isPlaying =
!isPlaying;
if (
isPlaying
) {
playPauseButton.textContent =
"⏸ Pause";
} else {
playPauseButton.textContent =
"▶ Play";
}
}
);
這裡最重要的是:
isPlaying =
!isPlaying;
假設原本 true 經過 !true 就變成 false
再按一次 !false 又變回 true
所以可以一直 Play , Pause 切換。
不能直接停止:
requestAnimationFrame(
animate
);
因為如果完全停止 Animation Loop:
OrbitControls、Renderer、其他互動也可能一起停止更新。
所以比較適合的方法是:
Animation Loop → 繼續執行 但 天體位置 → 不更新
也就是把行星動畫包進:
if (
isPlaying
) {
...
}
接下來加入速度控制。HTML:
<div class="speed-control">
<label for="speedSlider">
Animation Speed
</label>
<input
type="range"
id="speedSlider"
min="0.1"
max="5"
step="0.1"
value="1"
>
<span id="speedValue">
1.0x
</span>
</div>
這次第一次使用 type="range" 也就是 Slider。
Slider,我們設定:
min="0.1"
max="5"
step="0.1"
value="1"
分別代表:
min → 最慢 0.1x
max → 最快 5x
step → 每次改變 0.1
value → 初始速度 1x
JavaScript:
let timeScale =
1;
1 代表正常速度
取得 Slider:
const speedSlider =
document.getElementById(
"speedSlider"
);
const speedValue =
document.getElementById(
"speedValue"
);
讀取 Slider 加入:
speedSlider.addEventListener(
"input",
() => {
timeScale =
Number(
speedSlider.value
);
speedValue.textContent =
timeScale.toFixed(1) +
"x";
}
);
這次使用 input 而不是 change。
因為 Slider 移動時,我們希望拖動就立刻更新,
而不是等放開 Slider 才改變。
HTML Input 的 speedSlider.value
拿到的其實是 String 像是 "2.5"
所以我們使用:
Number(
speedSlider.value
);
把它轉成 2.5 方便後面做數學運算。
我們原本:
const delta =
clock.getDelta();
現在加入:
const scaledDelta =
delta *
timeScale;
假設 delta = 0.016
當 timeScale = 1
就是 0.016 × 1 = 0.016
正常速度。
如果 timeScale = 2
則 0.016 × 2 = 0.032
所有動畫變快兩倍。
如果 timeScale = 0.5
就是 0.016 × 0.5 = 0.008
變成一半速度。
原本 Sun Rotation:
sun.rotation.y +=
0.5 *
delta;
改成:
sun.rotation.y +=
0.5 *
scaledDelta;
原本 Planet Animation:
planet.mesh.rotation.y +=
planet.rotationSpeed *
delta;
改成:
planet.mesh.rotation.y +=
planet.rotationSpeed *
scaledDelta;
公轉也改成:
planet.angle +=
planet.orbitSpeed *
scaledDelta;
這樣 Slider 就會同時影響 Planet Self Rotation + Planet Revolution
原本:
venusAtmosphere.rotation.y +=
0.15 *
delta;
改成:
venusAtmosphere.rotation.y +=
0.15 *
scaledDelta;
因此金星大氣也會跟著速度一起改變。
Moon 也要使用 scaledDelta
公轉:
moonAngle +=
moonOrbitSpeed *
scaledDelta;
自轉:
moon.rotation.y +=
0.4 *
scaledDelta;
因此我們的 Speed Slider 現在會影響:
Sun Rotation
Planet Rotation
Planet Revolution
Venus Atmosphere
Moon Rotation
Moon Revolution
也就是整個太陽系的時間倍率。
今天 Animation 的核心結構變成:
function animate() {
requestAnimationFrame(
animate
);
const delta =
clock.getDelta();
const scaledDelta =
delta *
timeScale;
if (
isPlaying
) {
// Sun
// Planets
// Venus Atmosphere
// Moon
}
controls.update();
renderer.render(
scene,
camera
);
}
這就是今天最重要的邏輯。
控制面板可以再加入:
.control-panel hr {
margin: 18px 0;
border: none;
border-top:
1px solid
rgba(
255,
255,
255,
0.15
);
}
.control-panel button {
width: 100%;
padding: 10px;
border: none;
border-radius: 8px;
background:
rgba(
255,
255,
255,
0.12
);
color: white;
font-size: 14px;
cursor: pointer;
}
.control-panel button:hover {
background:
rgba(
255,
255,
255,
0.2
);
}
.speed-control {
margin-top: 18px;
}
.speed-control label {
margin-bottom: 8px;
}
.speed-control input {
width: 100%;
margin: 8px 0;
}
#speedValue {
display: block;
text-align: right;
color: #aaaaaa;
font-size: 13px;
}
今天控制的是 動畫是否運行 + 動畫運行速度
現在 UI 已經可以做到:
Solar System Controls
☑ 顯示軌道
☑ 金星大氣層
☑ 星空背景⏸ Pause
Animation Speed
0.1x ─────●───── 5.0x
其中最重要的兩個變數:
let isPlaying =
true;
let timeScale =
1;
分別負責:
isPlaying → 播放 / 暫停
timeScale → 時間倍率
再搭配:
const scaledDelta =
delta *
timeScale;
我們就可以讓整個太陽系使用同一套時間速度。
「Raycaster:用滑鼠選擇行星」
目前我們的控制方式主要集中在右上角 UI
Day 26 要開始直接操作 3D Scene
我們會加入 THREE.Raycaster
讓使用者直接滑鼠點擊哪一顆行星 則被選中的行星:對應 Orbit 會改變顏色。
也就是從 Day 26 開始,我們會真正做到滑鼠和3D物件的互動。